package wangxiaotao.shiro.filter;

import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import wangxiaotao.utils.JWTUtils;
import wangxiaotao.utils.RedisUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *自定义shiro拦截器
 * 先执行Shiro自己的Filter链； 再执行Servlet容器的Filter链（即原始的 Filter）。
 */
@Slf4j
public class CustomFilter extends AccessControlFilter {


    private final static String tokenKeyPrefix = "token:cache:";


    /**
     * step1:拒绝未登录请求，将请求交给onAccessDenied方法
     * 是否允许访问
     * @param request
     * @param response
     * @param mappedValue mappedValue 就是 [urls] 配置中拦截器参数部分
     * @return true表示允许
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        log.debug("------------>CustomFilter step1:拒绝请求给onAccessDenied");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        log.info("------------> request url:{}",((HttpServletRequest) request).getRequestURI());
        //将跨域请求放行
        return RequestMethod.OPTIONS.name().equals(httpServletRequest.getMethod());
    }

    /**
     * step2:拦截的未登录的请求首先在这里处理 获取 token后调用executeLogin方法
     * @param servletRequest
     * @param servletResponse
     * @return 如果返回true表示自己不处理且继续拦截器链执行，返回false表示自己已经处理了（比如重定向到另一个页面）。
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        log.debug("------------>CustomFilter step2:拦截的请求首先在这里处理");
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //获取请求头携带的token
        String token = request.getHeader("authorization");
        //判断是否有token
        if (StrUtil.isEmpty(token)){//没有token就拦截
            //转发异常
            log.error("------------>token为空：{}",token);
            request.getRequestDispatcher("/common/exception?e="+"请先登录").forward(request,response);
            return false;
        }
        String id;
        try {
            //获取token里面的用户id  可能会有异常
            id = JWTUtils.getClaimByName(token,"id");
        } catch (JwtException e) {
            //转发异常
            log.error("------------>token过期：{}",token);
            request.getRequestDispatcher("/common/exception?e="+"请重新登录").forward(request,response);
            return false;
        }
        //获取redis里存储的tokenId
        String relTokenId = (String)RedisUtils.get(getTokenKeyPrefix(Integer.parseInt(id)));
        if (StrUtil.isBlank(relTokenId)){
            //转发异常
            log.error("------------>redis里不存在该tokenId：{}",relTokenId);
            request.getRequestDispatcher("/common/exception?e="+"请重新登录").forward(request,response);
            return false;
        }
        //获取当前token头的tokenId
        String tokenId = JWTUtils.getId(token);
        //判断tokenId是否为活跃的tokenId
        if (relTokenId.equals(tokenId)){
            log.error("------------>正常token获取请求：{}",tokenId);
            return true;
        }else {
            Subject subject = SecurityUtils.getSubject();
            //如果为登录过的用户，就退出登录
            if (subject.isAuthenticated()||subject.isRemembered()) {
                subject.logout();
            }
            //转发异常
            log.error("------------>用户被挤下线tokenId：{}",relTokenId);
            request.getRequestDispatcher("/common/exception?e="+"账号在别处登录，如非本人操作请立即修改密码！").forward(request,response);
            return false;
        }
    }

    /**
     * 请求预处理   跨域处理
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     * https://www.cnblogs.com/zhaodagang8/p/11275685.html：跨域option的理解
     */
    @Override
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        log.debug("------------>CustomFilter 请求预处理");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers","Authorization,Origin, X-Requested-With, Content-Type, Accept,Access-Token");
        //如果是跨域请求
        if(RequestMethod.OPTIONS.name().equals(httpServletRequest.getMethod())){
            log.debug("------------>CustomFilter 这是跨域请求：{}",httpServletRequest.getRequestURI());
            //正常返回
            httpServletResponse.setStatus(200);
            return true;
        }
        //不是跨域请求执行父类的onPreHandle
        return super.onPreHandle(request, response, mappedValue);
    }

    private String getTokenKeyPrefix(Integer id){
        return tokenKeyPrefix+id;
    }
}
